#***********************************************************
#* Copyright (c) 2006, Zhimin Zhan.
#* Distributed open-source, see full license in MIT-LICENSE
#***********************************************************

require 'watir/watir_simple.rb'
require 'runit/assert'

module RWebUnit

  ##
  #  Wrapping WATIR and provide assertions
  #
  class WebTester
    include Watir::Simple
    include RUNIT::Assert

    attr_accessor :test_context

    def initialize
      @test_context = TestContext.new
    end

    def begin_at(relativeURL)
      url = @test_context.base_url  + relativeURL
      new_browser_at(url) # create @@browser
    end
    alias beginAt begin_at

    def closeBrowser
      begin
        puts "deprecated method: goBack, use close_browser instead"
        close_browser
      rescue
      end
    end

    def browser_opened?
      begin
        @@browser != nil
      rescue => e
        return false
      end
    end

    def goBack
      puts "deprecated method: goBack, use go_back instead"
      go_back()
    end

    def goForward
      puts "deprecated method: goBack, use go_forward instead"
      go_forward()
    end

    def goto_page(page)
      navigate_to(page)
    end
    alias gotoPage goto_page

    # assertions
    def assert_title_equals(title)
      assert_equals(title, html_title())
    end
    alias assertTitleEquals assert_title_equals

    # text fields
    def set_form_element(elementName, elementValue)
      enter_text_into_field_with_name(elementName, elementValue)
    end
    alias enterText set_form_element
    alias setFormElement set_form_element
    alias enter_text set_form_element

    def assert_text_present_in_textfield(textfieldName, text, msg = nil)
      assert_text_in_field(textfieldName, text, msg)
    end
    alias assertTextPresentInTextField assert_text_present_in_textfield
    
    #links
    def click_link(linkId)
      click_link_with_id(linkId)
    end
    alias clickLink click_link

    alias clickLinkWithText click_link_with_text
    
    def assert_link_present_with_exact_text(linkText)
      html_links.each { |link|
        return if linkText == link.text
      }
      fail( "can't find the link with text: #{linkText}")
    end
    alias assertLinkPresentWithExactText assert_link_present_with_exact_text
    
    def assert_link_not_present_with_exact_text(linkText)
      html_links.each { |link|
        assert(linkText != link.text, "unexpected link (exact): #{linkText} found")
      }
    end
    alias assertLinkNotPresentWithExactText assert_link_not_present_with_exact_text
    
    def assert_link_present_with_text(linkText)
      html_links.each { |link|
        return if link.text.include?(linkText)
      }
      fail( "can't find the link containing text: #{linkText}")
    end
    alias assertLinkPresentWithText assert_link_present_with_text
    
    def assert_link_not_present_with_text(linkText)
      html_links.each { |link|
        assert(!link.Text.include?(linkText), "unexpected link containing: #{linkText} found")
      }
    end
    alias assertLinkNotPresentWithText assert_link_not_present_with_text


    ##
    # buttons

    # submit first submit button
    def submit(buttonName = nil)
      if (buttonName.nil?) then
        html_buttons.each { |button|
          next if button.type != 'submit'
          button.click
          return
        }
      else
        click_button_with_name(buttonName)
      end
    end

    def clickButton(buttonId)
      click_button_with_id(buttonId)
    end
    alias click_button click_button_with_id

    def clickButtonWithCaption(caption)
      puts "deprecated, using click_button_with_caption instead"
      click_button_with_caption(caption)
    end

    def clickButtonWithValue(value)
      puts "deprecated, using click_button_with_value instead"
      click_button_with_value(value)
    end

    def assert_button_not_present(buttonId)
      html_buttons.each { |button|
        assert(button.id != buttonId, "unexpected button id: #{buttonId} found")
      }
    end
    alias assertButtonNotPresent assert_button_not_present
    
    def assert_button_not_present_with_text(text)
      html_buttons.each { |button|
        assert(button.value != text, "unexpected button id: #{text} found")
      }
    end
    alias assertButtonNotPresentWithText assert_button_not_present_with_text
    
    def assert_button_present(buttonID)
      html_buttons.each { |button|
        return if buttonID == button.id
      }
      assert(false, "can't find the button with id: #{buttonID}")
    end
    alias assertButtonPresent assert_button_present

    def assert_button_present_with_text(buttonText)
      html_buttons.each { |button|
        return if buttonText == button.value
      }
      assert(false, "can't find the button with text: #{buttonText}")
    end
    alias assertButtonPresentWithText assert_button_present_with_text
    
    # checkbox
    def check_checkbox(checkBoxName)
      @@browser.checkbox(:name, checkBoxName).set
    end
    alias checkCheckbox check_checkbox
    
    def uncheck_checkbox(checkBoxName)
      @@browser.checkbox(:name, checkBoxName).clear
    end
    alias uncheckCheckbox uncheck_checkbox 
    
    def assert_checkbox_not_selected(checkBoxName)
      html_checkboxes.each { |checkbox|
        if (checkbox.name == checkBoxName) then
          assert(!checkbox.isSet?, "Checkbox #{checkBoxName} checked unexpected")
        end
      }
    end
    alias assertCheckboxNotSelected assert_checkbox_not_selected
    
    def assert_checkbox_selected(checkBoxName)
      html_checkboxes.each { |checkbox|
        if (checkbox.name == checkBoxName) then
          assert(checkbox.isSet?, "Checkbox #{checkBoxName} not checked")
        end
      }
    end
    alias assertCheckboxSelected assert_checkbox_selected
    
    # combo box
    def select_option(selectName, option)
      @@browser.select_from_combobox_with_name(selectName, option)
    end
    alias selectOption select_option

    def assert_option_value_not_present(selectName, optionValue)
      html_selects.each { |select|
        continue unless select.name == selectName
        select.o.each do |option| # items in the list
          assert(!(option.value == optionValue), "unexpected select option: #{optionValue} for #{selectName} found")
        end
      }
    end
    alias assertOptionValueNotPresent assert_option_value_not_present
    
    def assert_option_not_present(selectName, optionLabel)
      html_selects.each { |select|
        next unless select.name == selectName
        select.o.each do |option| # items in the list
          assert(!(option.text == optionLabel), "unexpected select option: #{optionLabel} for #{selectName} found")
        end
      }
    end
    alias assertOptionNotPresent assert_option_not_present 

    def assert_option_value_present(selectName, optionValue)
      html_selects.each { |select|
        next unless select.name == selectName
        select.o.each do |option| # items in the list
          return if option.value == optionValue
        end
      }
      assert(false, "can't find the combob box with value: #{optionValue}")
    end
    alias assertOptionValuePresent assert_option_value_present

    def assert_option_present(selectName, optionLabel)
      html_selects.each { |select|
        next unless select.name == selectName
        select.o.each do |option| # items in the list
          return if option.text == optionLabel
        end
      }
      assert(false, "can't find the combob box: #{selectName} with value: #{optionLabel}")
    end
    alias assertOptionPresent assert_option_present
    
    def assert_option_equals(selectName, optionLabel)
      html_selects.each { |select|
        next unless select.name == selectName
        select.o.each do |option| # items in the list
          if (option.text == optionLabel) then
            assert_equal(select.value, option.value, "Select #{selectName}'s value is not equal to expected option label: '#{optionLabel}'")
          end
        end
      }
    end
    alias assertOptionEquals assert_option_equals

    def assert_option_value_equals(selectName, optionValue)
      html_selects.each { |select|
        next unless select.name == selectName
        assert_equal(select.value, optionValue, "Select #{selectName}'s value is not equal to expected: '#{optionValue}'")
      }
    end
    alias assertOptionValueEquals assert_option_value_equals

    #radio

    # radioGroup is the name field, radio options 'value' field
    def assert_radio_option_not_present(radioGroup, radioOption)
      html_radios.each { |radio|
        if (radio.name == radioGroup) then
          assert(!(radioOption == radio.value), "unexpected radio option: " + radioOption  + " found")
        end
      }
    end
    alias assertRadioOptionNotPresent assert_radio_option_not_present

    def assert_radio_option_present(radioGroup, radioOption)
      html_radios.each { |radio|
        return if (radio.name == radioGroup) and (radioOption == radio.value)
      }
      fail("can't find the radio option : '#{radioOption}'")
    end
    alias assertRadioOptionPresent assert_radio_option_present

    def assert_radio_option_selected(radioGroup, radioOption)
      html_radios.each { |radio|
        if (radio.name == radioGroup and radioOption == radio.value) then
          assert(radio.isSet?, "Radio button #{radioGroup}-[#{radioOption}] not checked")
        end
      }
    end
    alias assertRadioOptionSelected assert_radio_option_selected

    def assert_radio_option_not_selected(radioGroup, radioOption)
      html_radios.each { |radio|
        if (radio.name == radioGroup and radioOption == radio.value) then
          assert(!radio.isSet?, "Radio button #{radioGroup}-[#{radioOption}] checked unexpected")
        end
      }
    end
    alias assertRadioOptionNotSelected assert_radio_option_not_selected
    
    # the method is protected in JWebUnit
    def click_radio_option(radioGroup, radioOption)
      html_radios.each { |radio|
        if (radio.name == radioGroup and radioOption == radio.value) then
          radio.set
        end
      }
    end
    alias clickRadioOption click_radio_option
    
    #text
    def assert_text_present(text)
      assert((html_body().include? text), 'expected text: ' + text + ' not found')
    end
    alias assertTextPresent assert_text_present

    def assert_text_not_present(text)
      assert(!(html_body().include? text), 'expected text: ' + text + ' found')
    end
    alias assertTextNotPresent assert_text_not_present

    def assert_text_in_table(tableId, text)
      elem = @@browser.document.getElementById(tableId)
      assert_not_nil(elem, "tableId #{tableId} not exists")
      assert_equal(elem.tagName, "TABLE")
      puts elem.innerHTML if $DEBUG
      assert(elem.innerHTML.include?(text), "the text #{text} not found in table #{tableId}")
    end
    alias assertTextInTable assert_text_in_table

    def assert_text_not_in_table(tableId, text)
      elem = @@browser.document.getElementById(tableId)
      assert_equal(elem.name.uppercase, "TABLE")
      assert_not_nil(elem, "tableId #{tableId} not exists")
      assert(!elem.innerHTML.include?(text), "unexpected text #{text} found in table #{tableId}")
    end
    alias assertTextNotInTable assert_text_not_in_table
    
    def assert_element_present(elementID)
      assert_not_nil(@@browser.document.getElementById(elementID), "element with id #{elementID} not found")
    end
    alias assertElementPresent assert_element_present

    def assert_element_not_present(elementID)
      assert_nil(@@browser.document.getElementById(elementID), "unexpected element with id #{elementID} found")
    end
    alias assertElementNotPresent assert_element_not_present

    def assert_text_in_element(elementID, text)
      assertElementPresent(elementID)
      elem = @@browser.document.getElementById(elementID)
      assert_not_nil(elem.innerText, "element #{elementID} has no text")
      assert(elem.innerText.include?(text), "the text #{text} not found in element #{elementID}")
    end
    alias assertTextInElement assert_text_in_element
    
    def get_element_value(elementId)
      assertElementPresent(elementId)
      elem = @@browser.document.getElementById(elementId)
      elem_val = elem.invoke('innerText')
    end
    alias getElementValue get_element_value
    
    # new API
    def get_html_in_element(elementId)
      elem = @@browser.document.getElementById(elementId)
      assert_not_nil(elem, "HTML element: #{elementId} not exists")
      elem.innerHTML
    end
    alias getHtmlInElement get_html_in_element

    def get_element_by_id(elementId)
      elem = @@browser.document.getElementById(elementId)
    end
    alias getElementById get_element_by_id

    # ---
    # For deubgging
    # ---
    def dump_response(stream = nil)
      if stream.nil?
        puts html_body # std out
      else
        stream.puts html_body
      end
    end
    alias dumpResponse dump_response
    
    private
    def html_body
      @@browser.html()
    end

    def html_title
      @@browser.document.title
    end

    def html_links
      @@browser.links
    end

    def html_buttons
      @@browser.buttons
    end

    def html_selects
      @@browser.select_lists
    end

    def html_checkboxes
      @@browser.checkboxes
    end

    def html_radios
      @@browser.radios
    end

    def assert_equals(expected, actual, msg=nil)
      assert(expected == actual, (msg.nil?) ? "Expected: #{expected} diff from actual: #{actual}" : msg)
    end
    alias assert_equal assert_equals

    def assert_nil(actual, msg="")
      assert(actual.nil?, msg)
    end

    def assert_not_nil(actual, msg="")
      assert(!actual.nil?, msg)
    end

    def fail(message)
      assert(false, message)
    end

  end

end
